package me.helllp.dake.iam.core.service.impl;

import lombok.extern.slf4j.Slf4j;
import me.helllp.dake.common.MessageCode;
import me.helllp.dake.common.exception.DakeAuthException;
import me.helllp.dake.common.exception.DakeException;
import me.helllp.dake.core.bean.DakeAuthAccount;
import me.helllp.dake.core.component.IDakeCacheManager;
import me.helllp.dake.core.utils.CacheKeyUtil;
import me.helllp.dake.iam.core.DakeIamCoreConfig;
import me.helllp.dake.iam.core.IamCoreConst;
import me.helllp.dake.iam.core.bean.DakeAuthTicket;
import me.helllp.dake.iam.core.bo.RoleAndUrlBo;
import me.helllp.dake.iam.core.config.KaptchaConfig;
import me.helllp.dake.iam.core.dto.AuthDto;
import me.helllp.dake.iam.core.dto.LoginDto;
import me.helllp.dake.iam.core.logic.auth.IAccountLogic;
import me.helllp.dake.iam.core.mapper.DakeRoleMapper;
import me.helllp.dake.iam.core.service.IAuthService;
import me.helllp.dake.iam.core.vo.LoginVo;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.AntPathMatcher;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
@Slf4j
public class AuthService implements IAuthService {
    @Autowired
    private IAccountLogic accountLogic;

    @Autowired
    private IDakeCacheManager cacheManager;

    @Autowired
    private DakeIamCoreConfig iamCoreConfig;

    /**
     * 锁定账号的时间，可以退避
     */
    private static Map<Integer, Long> LOCK_TIME_MAP = new HashMap(){{
        put(1, 5 * 60);         // 5分钟
        put(2, 15 *60);         // 15分钟
        put(3, 60 * 60);        // 1小时
        put(4, 8 * 60 * 60);    // 8小时
    }};

    @Override
    public LoginVo login(LoginDto loginDto) throws DakeException {
        LoginVo vo = beforeLogin(loginDto);

        if (!vo.getIsRight()) {
            return vo;
        }

        DakeAuthTicket ticket = accountLogic.login(loginDto.getAccount(), loginDto.getPassword(), loginDto.getTenantId());
        if (ObjectUtils.isEmpty(ticket)) {
            loginFailed(loginDto);
            return LoginVo.builder().verifyPic(genVerifyCode(loginDto.getTenantId(), loginDto.getAccount()))
                    .isRight(false).code(MessageCode.ERROR_AUTHENTICATION).build();
        }
        return loginSuccess(ticket);
    }

    @Override
    public String genVerifyCode(Long tenantId, String account) {
        // TODO 临时使用固定验证码
        String code = "12473";
        // 设置验证码 login:verify:{tenantId}:{account}={验证码内容}
        String base64 = KaptchaConfig.genVerifyBase64(code);
        // 失效时间为1分钟
        cacheManager.putCache(CacheKeyUtil.genKey(IamCoreConst.LOGIN_VERIFY, tenantId, account), code, 60, TimeUnit.SECONDS);
        return base64;
    }

    private final AntPathMatcher pathMatcher = new AntPathMatcher();

    @Override
    public Boolean auth(DakeAuthAccount account, AuthDto authDto) {
        // 根据当前用户信息获取角色对应资源列表
        return pathMatcher.match("abc", authDto.getUrl());
    }

    @Autowired
    private DakeRoleMapper roleMapper;

    @Override
    public Boolean putAuthCache(Long tenantId, Long applicationId) {
        List<RoleAndUrlBo> list = roleMapper.queryRoleApis(tenantId, applicationId);
        Map<String,List<RoleAndUrlBo>> maps = list.stream().collect(Collectors.groupingBy(data -> data.getTenantId() + ":" + data.getApplicationId() + ":" + data.getRoleName()));

        maps.keySet().stream().forEach(key -> {
            Set<String> set = transfer(maps.get(key));
            cacheManager.putSet(key, set);
        });
        return true;
    }

    private static Set<String> transfer(List<RoleAndUrlBo> list) {
        return list.stream().filter(data -> !StringUtils.isBlank(data.getMethod()) && !StringUtils.isBlank(data.getApi())).map(data -> data.getMethod().toUpperCase() + data.getApi()).collect(Collectors.toSet());
    }


    private LoginVo beforeLogin(LoginDto loginDto) {
        // login:locked:{tenantId}:{account}={锁定时间}
        if (cacheManager.hasKey(CacheKeyUtil.genKey(IamCoreConst.LOGIN_LOCKED, loginDto.getTenantId(), loginDto.getAccount()))) {
            return LoginVo.builder().isRight(false).code(MessageCode.MSG_AUTH_USER_LOCKED).build();
        }

        // login:verify:{tenantId}:{account}={验证码内容}
        // 验证码校验
        String verifyCode = (String) cacheManager.getCache(CacheKeyUtil.genKey(IamCoreConst.LOGIN_VERIFY, loginDto.getTenantId(), loginDto.getAccount()));
        if (!StringUtils.equals(verifyCode, loginDto.getVerifyCode())) {
            return LoginVo.builder().isRight(false).code(MessageCode.MSG_AUTH_MUST_VERIFYCODE).build();
        }

        return  LoginVo.builder().isRight(true).code(MessageCode.OK_NORMAL).build();
    }

    private void loginFailed(LoginDto loginDto) {
        // login:errNum:{tenantId}:{account}={失败次数} 登录失败次数+1
        String errNumKey = CacheKeyUtil.genKey(IamCoreConst.LOGIN_ERR_NUM, loginDto.getTenantId(), loginDto.getAccount());
        Integer num = (Integer) cacheManager.getCache(errNumKey);
        num = (num == null ? 0 : num) + 1;
        cacheManager.putCache(errNumKey, num);

        // 设置锁定账号 login:locked:{tenantId}:{account}={锁定时间}
        Long lockTime = LOCK_TIME_MAP.get(num / 5);
        if (lockTime != null) {
            cacheManager.putCache(CacheKeyUtil.genKey(IamCoreConst.LOGIN_LOCKED, loginDto.getTenantId(), loginDto.getAccount()),
                lockTime,
                lockTime,
                TimeUnit.SECONDS);
        }
    }

    private LoginVo loginSuccess(DakeAuthTicket ticket) {
        // 登录凭证 token:{token}={dakeAuthAccount}
        cacheManager.putCache(
                CacheKeyUtil.genKey(IamCoreConst.LOGIN_TOKEN, ticket.getAuthToken()),
                ticket.getAuthUser(),
                iamCoreConfig.getLifespan(),
                TimeUnit.SECONDS);
        // account:{tenantId}:{account}={token}
        cacheManager.putCache(CacheKeyUtil.genKey(IamCoreConst.LOGIN_ACCOUNT, ticket.getAuthUser().getTenantId(), ticket.getAuthUser().getUserAccount()),
                ticket.getAuthToken(),
                iamCoreConfig.getLifespan(),
                TimeUnit.SECONDS);
        // 清理登录状态 login:errNum:{tenantId}:{account}={失败次数}
        cacheManager.cleanCache(CacheKeyUtil.genKey(IamCoreConst.LOGIN_ERR_NUM, ticket.getAuthUser().getTenantId(), ticket.getAuthUser().getUserAccount()));
        cacheManager.cleanCache(CacheKeyUtil.genKey(IamCoreConst.LOGIN_VERIFY, ticket.getAuthUser().getTenantId(), ticket.getAuthUser().getUserAccount()));
        return LoginVo.builder().token(ticket.getAuthToken()).isRight(true).build();
    }

    @Override
    public DakeAuthAccount verifyToken(String token) throws DakeAuthException {
        DakeAuthAccount authUser = (DakeAuthAccount)cacheManager.getCache(token);
        if (ObjectUtils.isEmpty(authUser)) {
            // 验证失败
            log.error("authentication failed");
            throw new DakeAuthException();
        }

        // 进行失效时间刷新
        cacheManager.putCache(token, authUser, iamCoreConfig.getLifespan(), TimeUnit.SECONDS);

        return authUser;
    }

    @Override
    public Boolean logout(String token) throws DakeAuthException {
        verifyToken(token);
        DakeAuthAccount authUser = (DakeAuthAccount)cacheManager.getCache(token);

        cacheManager.cleanCache(CacheKeyUtil.genKey(IamCoreConst.LOGIN_TOKEN, token));
        cacheManager.cleanCache(CacheKeyUtil.genKey(IamCoreConst.LOGIN_ACCOUNT, authUser.getTenantId(), authUser.getUserAccount()));
        return true;
    }

}
